/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.skatiger.jrove.tag;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.el.ELException;

import org.skatiger.jrove.JroveException;
import org.skatiger.jrove.JroveHandler;
import org.skatiger.jrove.TagConfig;
import org.skatiger.jrove.TagHandler;
import org.skatiger.jrove.TagLibrary;

public abstract class AbstractTagLibrary implements TagLibrary {

	private static class ComponentConfigWrapper implements ComponentConfig {

		protected final TagConfig parent;

		protected final String componentClass;
		protected final String rendererType;

		public ComponentConfigWrapper(TagConfig parent, String componentClass,
				String rendererType) {
			this.parent = parent;
			this.componentClass = componentClass;
			this.rendererType = rendererType;
		}

		public String getComponentClass() {
			return componentClass;
		}

		public String getRendererType() {
			return rendererType;
		}

		public JroveHandler getNextHandler() {
			return this.parent.getNextHandler();
		}

		public Tag getTag() {
			return this.parent.getTag();
		}

		public String getTagId() {
			return this.parent.getTagId();
		}
	}

	private static class ComponentHandlerFactory implements TagHandlerFactory {

		protected final String componentClass;
		protected final String rendererType;

		public ComponentHandlerFactory(String componentClass,
				String rendererType) {
			this.componentClass = componentClass;
			this.rendererType = rendererType;
		}

		public TagHandler createHandler(TagConfig cfg) throws JroveException,
				ELException {
			ComponentConfig ccfg = new ComponentConfigWrapper(cfg,
					this.componentClass, this.rendererType);
			return new ComponentHandler(ccfg);
		}
	}

	private static class HandlerFactory implements TagHandlerFactory {
		private final static Class<?>[] CONSTRUCTOR_SIG = new Class[] { TagConfig.class };

		protected final Class<? extends TagHandler> handlerType;

		public HandlerFactory(Class<? extends TagHandler> handlerType) {
			this.handlerType = handlerType;
		}

		public TagHandler createHandler(TagConfig cfg) throws JroveException,
				ELException {
			try {
				return this.handlerType.getConstructor(CONSTRUCTOR_SIG)
						.newInstance(new Object[] { cfg });
			} catch (InvocationTargetException ite) {
				Throwable t = ite.getCause();
				if (t instanceof JroveException) {
					throw (JroveException) t;
				} else if (t instanceof ELException) {
					throw (ELException) t;
				} else {
					throw new JroveException("Error Instantiating: "
							+ this.handlerType.getName(), t);
				}
			} catch (Exception e) {
				throw new JroveException("Error Instantiating: "
						+ this.handlerType.getName(), e);
			}
		}
	}

	private static class UserComponentHandlerFactory implements
			TagHandlerFactory {

		private final static Class<?>[] CONS_SIG = new Class[] { ComponentConfig.class };

		protected final String componentClass;
		protected final String rendererType;

		protected final Class<? extends TagHandler> type;

		protected final Constructor<? extends TagHandler> constructor;

		/**
		 * @param handlerType
		 */
		public UserComponentHandlerFactory(String componentClass,
				String rendererType, Class<? extends TagHandler> type) {
			this.componentClass = componentClass;
			this.rendererType = rendererType;
			this.type = type;
			try {
				this.constructor = this.type.getConstructor(CONS_SIG);
			} catch (Exception e) {
				throw new JroveException(
						"Must have a Constructor that takes in a ComponentConfig",
						e);
			}
		}

		public TagHandler createHandler(TagConfig cfg) throws JroveException,
				ELException {
			try {
				ComponentConfig ccfg = new ComponentConfigWrapper(cfg,
						this.componentClass, this.rendererType);
				return (TagHandler) this.constructor
						.newInstance(new Object[] { ccfg });
			} catch (InvocationTargetException e) {
				throw new JroveException(e.getCause().getMessage(), e
						.getCause().getCause());
			} catch (Exception e) {
				throw new JroveException(
						"Error Instantiating ComponentHandler: "
								+ this.type.getName(), e);
			}
		}
	}

	private static class UserTagFactory implements TagHandlerFactory {
		protected final URL location;

		public UserTagFactory(URL location) {
			this.location = location;
		}

		public TagHandler createHandler(TagConfig cfg) throws JroveException,
				ELException {
			return new UserTagHandler(cfg, this.location);
		}
	}

	private final Map<String, TagHandlerFactory> factories;

	private final String namespace;

	private final Map<String, Method> functions;

	public AbstractTagLibrary(String namespace) {
		this.namespace = namespace;
		this.factories = new HashMap<String, TagHandlerFactory>();
		this.functions = new HashMap<String, Method>();
	}

	protected final void addComponent(String name, String componentClass) {
		this.addComponent(name, componentClass, "");
	}

	protected final void addComponent(String name, String componentClass,
			String rendererType) {
		this.factories.put(name, new ComponentHandlerFactory(componentClass,
				rendererType));
	}

	protected final void addComponent(String name, String componentClass,
			Class<? extends TagHandler> handlerType) {
		this.addComponent(name, componentClass, null, handlerType);
	}

	protected final void addComponent(String name, String componentClass,
			String rendererType, Class<? extends TagHandler> handlerType) {
		this.factories.put(name, new UserComponentHandlerFactory(
				componentClass, rendererType, handlerType));
	}

	protected final void addFunction(String name, Method method) {
		this.functions.put(name, method);
	}

	protected final void addTagHandler(String name,
			Class<? extends TagHandler> handlerType) {
		this.factories.put(name, new HandlerFactory(handlerType));
	}

	protected final void addUserTag(String name, URL source) {
		this.factories.put(name, new UserTagFactory(source));
	}

	public boolean containsComponent(String ns, String componentClass) {
		if (this.namespace.equals(ns)) {
			return this.functions.containsKey(componentClass);
		}
		return false;
	}

	public boolean containsFunction(String ns, String name) {
		if (this.namespace.equals(ns)) {
			return this.functions.containsKey(name);
		}
		return false;
	}

	public boolean containsNamespace(String ns) {
		return this.namespace.equals(ns);
	}

	public boolean containsTagHandler(String ns, String localName) {
		if (this.namespace.equals(ns)) {
			if (this.factories.containsKey(localName)) {
				return true;
			}
		}
		return false;
	}

	public Method createFunction(String ns, String name) {
		if (this.namespace.equals(ns)) {
			return (Method) this.functions.get(name);
		}
		return null;
	}

	public TagHandler createTagHandler(String ns, String localName,
			TagConfig tag) throws JroveException {
		if (this.namespace.equals(ns)) {
			TagHandlerFactory f = (TagHandlerFactory) this.factories
					.get(localName);
			if (f != null) {
				return f.createHandler(tag);
			}
		}
		return null;
	}

	public String getNamespace() {
		return namespace;
	}

	@Override
	public String toString() {
		return "jrove-taglib namespace:" + namespace;
	}
}
